Micron Document
NexusPi Git Node

Commit 87b743b9d295fc711a822e4454cdb8b4a1172884


Parents : a3c39cf
Author : James L <jrl290@gmail.com>
Date : 2026-07-18T12:28:20-04:00

fix: remove backbone hop decrement — backbone is a network interface, not a shared instance

The Python reference only decrements hops for is_local_client_interface()
or interface_to_shared_instance() — never for backbone/network interfaces.
The backbone (rmap.world) is a remote server reached via HTTP, not a local
client at 0 network hops. Decrementing its hop count caused:

- Path table stored announce_hops=1 instead of 2 for destinations behind
the backbone, making RTNode think they were directly reachable
- Transport forwarding produced HEADER_1/BROADCAST instead of the correct
HEADER_2/TRANSPORT with rmap.world as next hop
- rmap.world received bare broadcasts it couldn't forward, silently
dropping Sideband→Meshchat traffic

Also included: RNode_Firmware.ino receive path fixes (raw-shift removal,
phy_header removal, side-channel delivery, split trailing-frame fix),
Transport.h dump_whitelists reorg, and diagnostic logging.

Changes

3 files changed, 125 insertions(+), 93 deletions(-)


Diff

diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino
index ed4008e..2448521 100755
--- a/RNode_Firmware.ino
+++ b/RNode_Firmware.ino
@@ -95,8 +95,6 @@ volatile uint16_t queued_bytes = 0;
volatile uint16_t queue_cursor = 0;
volatile uint16_t current_packet_start = 0;
volatile bool serial_buffering = false;
-static uint8_t last_lora_phy_header = 0;
-static bool last_lora_phy_header_valid = false;
#if HAS_BLUETOOTH || HAS_BLE == true
bool bt_init_ran = false;
#endif
@@ -111,7 +109,6 @@ static bool last_lora_phy_header_valid = false;
size_t len;
int rssi;
int snr_raw;
- uint8_t phy_header;
uint8_t data[];
} modem_packet_t;
static xQueueHandle modem_packet_queue = NULL;
@@ -1277,13 +1274,6 @@ inline void kiss_write_packet() {
// CBA RESERVE
//RNS::Bytes data();
RNS::Bytes data(512);
-#ifdef FIREWALL_MODE
- if (last_lora_phy_header_valid && host_write_len > 2 && pbuf[1] > 16) {
- VERBOSEF("[LoRa] RX raw-shift fix: prepend 0x%02x (hops byte was %u)",
- last_lora_phy_header, (unsigned)pbuf[1]);
- data << last_lora_phy_header;
- }
-#endif
for (uint16_t i = 0; i < host_write_len; i++) {
#if MCU_VARIANT == MCU_NRF52
portENTER_CRITICAL();
@@ -1295,7 +1285,6 @@ inline void kiss_write_packet() {
data << byte;
}
lora_interface.handle_incoming(data);
- last_lora_phy_header_valid = false;
#endif
serial_write(FEND);
@@ -1358,40 +1347,33 @@ void ISR_VECT receive_callback(int packet_size) {
uint8_t sequence = packetSequence(header);
bool ready = false;
- #ifdef FIREWALL_MODE
- // Some Reticulum LoRa peers transmit raw RNS frames without the
- // RNode split/framing byte. If we strip the first byte in that case,
- // the RNS header shifts left and packets unpack as nonsense hops and
- // contexts. Non-split RNode framing uses a low nibble of 0; split
- // RNode frames are full-size fragments. Raw RNS control/announce
- // frames seen here have a non-zero low nibble and fit in one LoRa frame.
- if ((header & 0x0F) != 0 && (packet_size + 1) < SINGLE_MTU) {
- read_len = 0;
- pbuf[read_len++] = header;
- getPacketData(packet_size);
- ready = true;
- }
- else
- #endif
-
if (isSplitPacket(header) && seq == SEQ_UNSET) {
- // This is the first part of a split
- // packet, so we set the seq variable
- // and add the data to the buffer
- #if MCU_VARIANT == MCU_NRF52
- int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask);
- #else
- read_len = 0;
- #endif
-
- seq = sequence;
+ // Trailing empty frame from a just-completed split? The
+ // transmit-side off-by-one emits a frame with 0 data bytes
+ // when the raw size is an exact multiple of the frame cap.
+ // Recognise it by matching last_seq and discard without
+ // poisoning state.
+ if (packet_size == 0 && sequence == last_seq) {
+ // Belongs to the previous split — nothing to do.
+ } else {
+ // This is the first part of a split
+ // packet, so we set the seq variable
+ // and add the data to the buffer
+ #if MCU_VARIANT == MCU_NRF52
+ int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask);
+ #else
+ read_len = 0;
+ #endif
+
+ seq = sequence;
- #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52
- last_rssi = LoRa->packetRssi();
- last_snr_raw = LoRa->packetSnrRaw();
- #endif
+ #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52
+ last_rssi = LoRa->packetRssi();
+ last_snr_raw = LoRa->packetSnrRaw();
+ #endif
- getPacketData(packet_size);
+ getPacketData(packet_size);
+ }
} else if (isSplitPacket(header) && seq == sequence) {
// This is the second part of a split
@@ -1403,6 +1385,7 @@ void ISR_VECT receive_callback(int packet_size) {
#endif
getPacketData(packet_size);
+ last_seq = sequence; // remember for trailing-empty-frame detection
seq = SEQ_UNSET;
ready = true;
@@ -1431,23 +1414,60 @@ void ISR_VECT receive_callback(int packet_size) {
// flag to true.
if (seq != SEQ_UNSET) {
- // If we already had part of a split
- // packet in the buffer, we clear it.
- #if MCU_VARIANT == MCU_NRF52
- int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask);
+ // A split-packet reassembly is in progress. Deliver this
+ // non-split packet through a side channel without touching
+ // pbuf or seq — read straight from the LoRa FIFO into a
+ // modem_packet allocation.
+ #if MCU_VARIANT == MCU_ESP32 || MCU_VARIANT == MCU_NRF52
+ {
+ modem_packet_t *mp = modem_packet_alloc(packet_size);
+ if (mp) {
+ #if MCU_VARIANT == MCU_ESP32
+ mp->snr_raw = LoRa->packetSnrRaw();
+ mp->rssi = LoRa->packetRssi(mp->snr_raw);
+ #endif
+ mp->len = packet_size;
+ for (uint16_t i = 0; i < packet_size; i++) {
+ mp->data[i] = LoRa->read();
+ }
+ if (!modem_packet_queue
+ || xQueueSendFromISR(modem_packet_queue, &mp, NULL) != pdPASS) {
+ modem_packet_free(mp);
+ }
+ }
+ // pbuf, read_len, and seq are untouched — frame 2 will
+ // still match when it arrives.
+ }
#else
- read_len = 0;
- #endif
- seq = SEQ_UNSET;
- }
+ // On single-threaded MCUs we cannot easily buffer a
+ // side-channel delivery, so fall back to the original
+ // behaviour: the in-progress split is discarded.
+ // (RTNode targets ESP32 / nRF52, so this path is
+ // not exercised in Firewall Mode.)
+ #if MCU_VARIANT == MCU_NRF52
+ int_mask = taskENTER_CRITICAL_FROM_ISR(); read_len = 0; taskEXIT_CRITICAL_FROM_ISR(int_mask);
+ #else
+ read_len = 0;
+ #endif
+ seq = SEQ_UNSET;
- #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52
- last_rssi = LoRa->packetRssi();
- last_snr_raw = LoRa->packetSnrRaw();
- #endif
+ #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52
+ last_rssi = LoRa->packetRssi();
+ last_snr_raw = LoRa->packetSnrRaw();
+ #endif
- getPacketData(packet_size);
- ready = true;
+ getPacketData(packet_size);
+ ready = true;
+ #endif
+ } else {
+ #if MCU_VARIANT != MCU_ESP32 && MCU_VARIANT != MCU_NRF52
+ last_rssi = LoRa->packetRssi();
+ last_snr_raw = LoRa->packetSnrRaw();
+ #endif
+
+ getPacketData(packet_size);
+ ready = true;
+ }
}
if (ready) {
@@ -1472,8 +1492,6 @@ void ISR_VECT receive_callback(int packet_size) {
modem_packet->snr_raw = LoRa->packetSnrRaw();
modem_packet->rssi = LoRa->packetRssi(modem_packet->snr_raw);
#endif
- modem_packet->phy_header = header;
-
// Send packet to event queue, but free the
// allocated memory again if the queue is
// unable to receive the packet.
@@ -1731,7 +1749,7 @@ void transmit(uint16_t size) {
for (uint16_t i=0; i < size; i++) {
LoRa->write(tbuf[i]); written++;
- if (written == 255 && isSplitPacket(header)) {
+ if (written == 255 && isSplitPacket(header) && (i + 1 < size)) {
if (!LoRa->endPacket()) {
kiss_indicate_error(ERROR_MODEM_TIMEOUT);
kiss_indicate_error(ERROR_TXFAILED);
@@ -2825,7 +2843,7 @@ void loop() {
uint32_t psram_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
Serial.printf("[HEAP] free=%u min=%u max_alloc=%u psram=%u\r\n",
free_heap, ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(), psram_free);
- // RNS::Transport::dump_whitelists();
+ RNS::Transport::dump_whitelists();
}
}
@@ -2914,8 +2932,6 @@ void loop() {
host_write_len = modem_packet->len;
last_rssi = modem_packet->rssi;
last_snr_raw = modem_packet->snr_raw;
- last_lora_phy_header = modem_packet->phy_header;
- last_lora_phy_header_valid = true;
memcpy(&pbuf, modem_packet->data, modem_packet->len);
modem_packet_free(modem_packet);
modem_packet = NULL;
@@ -2934,8 +2950,6 @@ void loop() {
if(modem_packet_queue && xQueueReceive(modem_packet_queue, &modem_packet, 0) == pdTRUE && modem_packet) {
memcpy(&pbuf, modem_packet->data, modem_packet->len);
host_write_len = modem_packet->len;
- last_lora_phy_header = modem_packet->phy_header;
- last_lora_phy_header_valid = true;
modem_packet_free(modem_packet);
modem_packet = NULL;

diff --git a/lib/microReticulum/src/Transport.cpp b/lib/microReticulum/src/Transport.cpp
index aa370f0..949206d 100755
--- a/lib/microReticulum/src/Transport.cpp
+++ b/lib/microReticulum/src/Transport.cpp
@@ -108,8 +108,6 @@ static void flatset_insert(std::vector<Bytes>& vec, const Bytes& key) {
/*static*/ std::set<Destination> Transport::_control_destinations;
/*static*/ std::set<Bytes> Transport::_control_hashes;
-
-
/*static*/ std::map<Bytes, Bytes> Transport::_pending_local_path_requests;
/*static*/ double Transport::_start_time = 0.0;
@@ -261,6 +259,22 @@ static const char* pkt_type_name(uint8_t t) {
default: return "?";
}
}
+// Human-readable context name for resource tracing
+static const char* ctx_name(uint8_t ctx) {
+ switch (ctx) {
+ case 0x01: return "RESOURCE";
+ case 0x02: return "RESOURCE_ADV";
+ case 0x03: return "RESOURCE_REQ";
+ case 0x04: return "RESOURCE_HMU";
+ case 0x05: return "RESOURCE_PRF";
+ case 0x06: return "RESOURCE_ICL";
+ case 0x07: return "RESOURCE_RCL";
+ default: return nullptr;
+ }
+}
+static inline bool is_resource_ctx(uint8_t ctx) {
+ return ctx >= 0x01 && ctx <= 0x07;
+}
/*static*/ Identity Transport::_identity({Type::NONE});
// CBA
@@ -380,8 +394,6 @@ static const char* pkt_type_name(uint8_t t) {
}
// TODO
-
-
//#ifndef NDEBUG
// CBA DEBUG
dump_stats();
@@ -1185,8 +1197,6 @@ static const char* pkt_type_name(uint8_t t) {
if (packet.hops() > 0) {
// TODO
-
-
bool queued_announces = (interface.announce_queue().size() > 0);
if (!queued_announces && outbound_time > interface.announce_allowed_at()) {
uint16_t wait_time = 0;
@@ -1556,8 +1566,6 @@ static const char* pkt_type_name(uint8_t t) {
};
// TODO
-
-
//if (packet_filter(packet)) {
// CBA
bool accept = true;
@@ -1749,6 +1757,10 @@ static const char* pkt_type_name(uint8_t t) {
if (!is_backbone_interface(packet.receiving_interface()) && packet.packet_type() != Type::Packet::ANNOUNCE) {
WLOG(packet, "TO: " + short_hash(packet.destination_hash()) + " (" + dest_zone(packet.destination_hash()) + ") - LAN-IN hops=" + std::to_string(packet.hops()) + " sz=" + std::to_string(packet.raw().size()));
}
+ // Resource packet trace: log any resource-context packet entering inbound
+ if (is_resource_ctx(packet.context())) {
+ WLOG(packet, std::string(ctx_name(packet.context())) + " IN hops=" + std::to_string(packet.hops()) + " sz=" + std::to_string(packet.raw().size()) + " dst=" + short_hash(packet.destination_hash()));
+ }
#endif
// Firewall mode: no local-client distinction. All devices are
@@ -2208,9 +2220,9 @@ static const char* pkt_type_name(uint8_t t) {
TRACE("Transport::inbound: Checking if packet is meant for link transport...");
auto link_iter = _link_table.find(packet.destination_hash());
if (link_iter != _link_table.end()) {
- DEBUG("LINK-XPORT: pkt for " + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()) + " hops=" + std::to_string(packet.hops()) + " from=" + packet.receiving_interface().toString() + " hdr=" + std::to_string(packet.header_type()) + " sz=" + std::to_string(packet.raw().size()));
LinkEntry& link_entry = (*link_iter).second;
- DEBUG("LINK-XPORT: entry hops=" + std::to_string(link_entry._hops) + " rem=" + std::to_string(link_entry._remaining_hops) + " recv=" + link_entry._receiving_interface.toString() + " out=" + link_entry._outbound_interface.toString() + " val=" + std::to_string(link_entry._validated));
+ NOTICE("LINK-XPORT: pkt " + packet.destination_hash().toHex().substr(0,8) + " type=" + std::to_string(packet.packet_type()) + " ctx=" + std::to_string(packet.context()) + " hops=" + std::to_string(packet.hops()) + " from=" + packet.receiving_interface().toString() + " sz=" + std::to_string(packet.raw().size()));
+ NOTICE("LINK-XPORT: entry hops=" + std::to_string(link_entry._hops) + " rem=" + std::to_string(link_entry._remaining_hops) + " recv=" + link_entry._receiving_interface.toString() + " out=" + link_entry._outbound_interface.toString() + " val=" + std::to_string(link_entry._validated));
// If receiving and outbound interface is
// the same for this link, direction doesn't
// matter, and we simply send the packet on.
@@ -2228,12 +2240,18 @@ static const char* pkt_type_name(uint8_t t) {
// the opposite interface of what the
// packet was received on.
if (packet.receiving_interface() == link_entry._outbound_interface) {
- // Also check that expected hop count matches
+ // Reverse direction (from destination back toward initiator).
+ // Match Python reference Transport.py line 1611: exact match.
+
+
if (packet.hops() == link_entry._remaining_hops) {
outbound_interface = link_entry._receiving_interface;
}
else {
- DEBUG("LINK-XPORT: HOP MISMATCH (from outbound) pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._remaining_hops));
+ NOTICE("LINK-XPORT: HOP MISMATCH (from outbound) pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._remaining_hops));
+ if (is_resource_ctx(packet.context())) {
+ WLOG(packet, std::string(ctx_name(packet.context())) + " HOP-MISMATCH rev pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._remaining_hops));
+ }
}
}
else if (packet.receiving_interface() == link_entry._receiving_interface) {
@@ -2242,16 +2260,22 @@ static const char* pkt_type_name(uint8_t t) {
outbound_interface = link_entry._outbound_interface;
}
else {
- DEBUG("LINK-XPORT: HOP MISMATCH (from receiving) pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._hops));
+ NOTICE("LINK-XPORT: HOP MISMATCH (from receiving) pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._hops));
+ if (is_resource_ctx(packet.context())) {
+ WLOG(packet, std::string(ctx_name(packet.context())) + " HOP-MISMATCH fwd pkt.hops=" + std::to_string(packet.hops()) + " expected=" + std::to_string(link_entry._hops));
+ }
}
}
else {
- DEBUG("LINK-XPORT: IFACE MISMATCH recv=" + packet.receiving_interface().toString() + " entry_recv=" + link_entry._receiving_interface.toString() + " entry_out=" + link_entry._outbound_interface.toString());
+ NOTICE("LINK-XPORT: IFACE MISMATCH recv=" + packet.receiving_interface().toString() + " entry_recv=" + link_entry._receiving_interface.toString() + " entry_out=" + link_entry._outbound_interface.toString());
}
}
if (outbound_interface) {
- DEBUG("LINK-XPORT: FWD to " + outbound_interface.toString());
+ NOTICE("LINK-XPORT: FWD to " + outbound_interface.toString());
+ if (is_resource_ctx(packet.context())) {
+ WLOG(packet, std::string(ctx_name(packet.context())) + " FWD to " + outbound_interface.toString() + " (" + zone_tag(is_backbone_interface(outbound_interface)) + ")");
+ }
// Add this packet to the filter hashlist now that
// we have determined it's actually our turn to
// process it (matching Python Transport line 1544).
@@ -2269,7 +2293,13 @@ static const char* pkt_type_name(uint8_t t) {
link_entry._timestamp = OS::time();
}
else {
- DEBUG("LINK-XPORT: DROPPED (no outbound interface resolved)");
+ NOTICE("LINK-XPORT: DROPPED (no outbound interface resolved)");
+ }
+ }
+ else {
+ NOTICE("LINK-XPORT: dest " + packet.destination_hash().toHex().substr(0,8) + " NOT in link_table (size=" + std::to_string(_link_table.size()) + ")");
+ if (is_resource_ctx(packet.context())) {
+ WLOG(packet, std::string(ctx_name(packet.context())) + " NO-LINKTABLE (size=" + std::to_string(_link_table.size()) + ")");
}
}
}
@@ -2394,8 +2424,6 @@ static const char* pkt_type_name(uint8_t t) {
bool rate_blocked = false;
// TODO
-
-
uint8_t retries = 0;
uint8_t announce_hops = packet.hops();
uint8_t local_rebroadcasts = 0;
@@ -3370,8 +3398,6 @@ Deregisters an announce handler.
}
return deque.size() < before;
}
-
-
/*
Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
@@ -3403,8 +3429,6 @@ will announce it.
if (on_interface && recursive) {
// TODO
-
-
bool queued_announces = (on_interface.announce_queue().size() > 0);
if (queued_announces) {
TRACE("Blocking recursive path request on " + on_interface.toString() + " due to queued announces");
@@ -3694,8 +3718,6 @@ will announce it.
char destination_table_path[Type::Reticulum::FILEPATH_MAXSIZE];
snprintf(destination_table_path, Type::Reticulum::FILEPATH_MAXSIZE, "%s/destination_table", Reticulum::_storagepath);
if (!_owner.is_connected_to_shared_instance() && OS::file_exists(destination_table_path)) {
-
-
try {
#if CUSTOM
TRACEF("Transport::start: buffer capacity %d bytes", Persistence::_buffer.capacity());
@@ -3857,8 +3879,6 @@ TRACEF("Transport::start: buffer size %d bytes", Persistence::_buffer.size());
}
DEBUGF("Trimmed path table from %d to %d destinations for persistence", _destination_table.size(), persist_table.size());
}
-
-
#if CUSTOM
{
Persistence::_document.set(persist_table);

diff --git a/lib/microReticulum/src/Transport.h b/lib/microReticulum/src/Transport.h
index f3acd7a..71afbc0 100755
--- a/lib/microReticulum/src/Transport.h
+++ b/lib/microReticulum/src/Transport.h
@@ -412,6 +412,7 @@ namespace RNS {
static void clean_caches();
static void clear_caches_in_memory(); // aggressive in-memory cache clearing for heap pressure
static void dump_stats();
+ static void dump_whitelists();
static void exit_handler();
static uint16_t remove_reverse_entries(const std::vector<Bytes>& hashes);
@@ -425,9 +426,6 @@ namespace RNS {
static bool packet_contains_whitelisted_address(const Packet& packet);
static const char* packet_whitelist_annotation(const Packet& packet);
- // CBA
- static void dump_whitelists();
-
// CBA
static void cull_path_table();

Served by rngit 1.5.4 - Generated in 0.04s